//
// xmlint.cpp
// xmlint
// An XML-based interface for the Protea Project / xmlint.cpp contains the main function
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include <time.h>

#include "../SDK/ToolsInterface.h"
#include "xmlparse.h"
#include "xmlapp.h"

#include <xercesc/framework/MemBufInputSource.hpp>
#include <xercesc/sax2/XMLReaderFactory.hpp>

// Fix for broken xerces under debug mode
#undef new

DAEMON_SERVICE( XCXMLAppInterface, xmlint, "xmlint", "Protea XML Interface", "Provides an XML interface server for the Protea Project", "GCS\0\0" );

// Define the Control Handler
XMLINT_CTRL_HANDLER;

fstream test_detail;

void XMLTraceIn( const char* text, ofstream* log_file ) {
	if (xmlint.tracing_mode)
		cout << "Input: " << endl << text << endl;
	if (log_file) {
		*log_file << "Input: " << endl << text << endl;
		log_file->flush();
	};
	DEBUGOUT( "INPUT:" );
	DEBUGOUT( text );
};

void XMLTraceOut( const char* text, ofstream* log_file ) {
	if (xmlint.tracing_mode)
		cout << "Output: " << endl << text << endl;
	if (log_file) {
		*log_file << "Output: " << endl << text << endl;
		log_file->flush();
	};
	DEBUGOUT( "OUTPUT:" );
	DEBUGOUT( text );
};




struct worker_thread_input {
	XCSocketTransport* socket;
};



// Helper thread
THREAD_PROCEDURE WorkerThread( void* param ) {
	char* buf = new char[ MAX_LEN ];
	char* text = NULL;
	bool working = true;
	long length = 0;
	bool bailout = false;
	XCXML_Protea* rootnode = NULL;
	socket_information* info = (socket_information*)param;
	XCSocketTransport* newsocket = info->transport;
	XCDefaultType defaults;
	memset( &defaults, 0, sizeof( XCDefaultType ) );

	// log_file file name for log_fileging
	ofstream* log_file = NULL;
	char* log_filefile = NULL;

	DEBUGOUT( "Started new thread" );

	xmlint_parser xml_parser;
	xml_parser.cursocket = newsocket;
	xml_parser.curinfo = info;

	DEBUGOUT( "Connection accepted - thread started" );
	printf( "Ready to receive transaction!\n" );

	// get all information from connection
	try {
		do {
			// Gather full data from the socket
			while ( working ) {
				length = newsocket->Recv( buf, MAX_LEN );
				buf[ length ] = 0;

				if (length) {
					if ( text ) {
						char* temp = new char[ strlen( text ) + length + 1 ];
						sprintf( temp, "%s%s", text, buf );
						delete[] text;
						text = temp;
					} else {
						text = new char[ length + 1 ];
						sprintf( text, "%s", buf );
					};

					// Check for end of transmission
					if ( memchr( buf, 0, length ) )
						working = false;
				} else {
					// We had an error of some sort, so abort
					working = false;
					bailout = true;
				};
			};


			if (length && !bailout) {
				// Output what we received
				XMLTraceIn( text, log_file );

				XERCES_CPP_NAMESPACE::MemBufInputSource* meminput = new XERCES_CPP_NAMESPACE::MemBufInputSource( (const XMLByte*)text, strlen( text ), "xmlint", false );
				XERCES_CPP_NAMESPACE::SAX2XMLReader* reader = XERCES_CPP_NAMESPACE::XMLReaderFactory::createXMLReader();

				reader->setContentHandler( &xml_parser );
				reader->setErrorHandler( &xml_parser );

				try {
					reader->parse( *meminput );
				} catch( XMLParseException& ex ) {
					if (xml_parser.transaction_id) {
						xprintf( false, newsocket, "%s<%s %s=\"1.0\">\n", xmltag, proteatag, versiontag );
						xprintf( false, newsocket, "<%s ApplNumber='%d'>", transactionlisttag, xml_parser.app_nbr );
						xprintf( false, newsocket, "<%s ID='%d' Success='0'>", transactiontag, xml_parser.transaction_id );
						xprintf( false, newsocket, "<%s>", messagelisttag );
						xprintf( false, newsocket, "\t<%s>%s</%s>\n", messagetag, ex.GetDescription(), messagetag );
						xprintf( true, newsocket, "</%s></%s></%s></%s>\n", messagelisttag, transactiontag, transactionlisttag, proteatag );
					} else {
	 					// We have an xml parse error, and need to send back a response
						xprintf( false, newsocket, "%s<%s %s=\"1.0\">\n", xmltag, proteatag, versiontag );
						xprintf( false, newsocket, "\t<%s>%s</%s>\n", errortag, ex.GetDescription(), errortag );
						xprintf( true, newsocket, "</%s>\n", proteatag );
					};
				} catch( XCToolsError& error ) {
					if (xml_parser.transaction_id) {
						xprintf( false, newsocket, "%s<%s %s=\"1.0\">\n", xmltag, proteatag, versiontag );
						xprintf( false, newsocket, "<%s ApplNumber='%d'>", transactionlisttag, xml_parser.app_nbr );
						xprintf( false, newsocket, "<%s ID='%d' Success='0'>", transactiontag, xml_parser.transaction_id );
						xprintf( false, newsocket, "<%s>", messagelisttag );
						xprintf( false, newsocket, "\t<%s>%s</%s>\n", messagetag, error.GetDescription(), messagetag );
						xprintf( true, newsocket, "</%s></%s></%s></%s>\n", messagelisttag, transactiontag, transactionlisttag, proteatag );
					} else {
						// There was an error reading the transaction id, so send back a generic error
						xprintf( false, newsocket, "%s<%s %s=\"1.0\">\n", xmltag, proteatag, versiontag );
						xprintf( false, newsocket, "\t<%s>%s</%s>\n", errortag, error.GetDescription(), errortag );
						xprintf( true, newsocket, "</%s>\n", proteatag );
					};
				} catch( ... ) {
					xprintf( false, newsocket, "%s<%s %s=\"1.0\">\n", xmltag, proteatag, versiontag );
					xprintf( false, newsocket, "\t<%s>%s</%s>\n", errortag, "Unknown error", errortag );
					xprintf( true, newsocket, "</%s>\n", proteatag );
				};
				xml_parser.Reset();

				delete reader;
				delete meminput;
			} else
				bailout = true;

			delete[] text;
			delete rootnode;
			text = NULL;
			rootnode = NULL;

			// reset for next connection
			working = true;
		} while ( !bailout );
	} catch( XCToolsError& error ) {
		XCOutputCallStack( NULL, error.GetErrorNumber(),error.GetDescription() );
	} catch( ... ) {
		DEBUGOUT( "Error reading from socket, closing thread..." );
	};
	delete [] buf;

	DEBUGOUT( "Closing thread..." );

	// Close the socket
	newsocket->Close();

	// Send abort transaction for each operation open on this socket
	xmlint.AbortSocketTrans( info );

	// Remove our reference to the socket information
	xmlint.RemoveSocketInfo( info );

	if ( log_file )
	{
		log_file->close();
		delete log_file;
	};
	if ( log_filefile )
		delete[] log_filefile;

	return 0;
};





// Listener thread
THREAD_PROCEDURE ListenerThread( void* param ) {
	bool failure = false;

	cout << "XML Server accepting connections" << endl;
	do {
		try {
			// Accept the new connection
			XCSocketTransport* transport = xmlint.listen_socket->Accept();

			// Allocate a new socket information structure
			socket_information* info = new socket_information;
			memset( info, 0, sizeof( socket_information ) );
			info->transport = transport;

			// Prepare the socket information
			xmlint.AddSocketInfo( info );

			// Actually start the thread now
			info->thread = XCCreateThread( WorkerThread, (void*)info );
		} catch( ... ) {
			DEBUGOUT( "Error accepting connection from listen socket" );
			failure= true;
		};
	} while (!failure);

	Protea::ThreadUninitialize();

	return 0;
};




// Service functions
void XCXMLAppInterface::OnInit() {
	DEBUGOUT( "OnInit" );

	// Attempt to connect to gcs
	char temp[ 255 ];
	sprintf( temp, "Connecting to %s, port %d\n", hostname, gcs_port );
	DEBUGOUT( temp );

	SetParent( Connect( hostname, gcs_port ) );

	// Initialize
	const char* process_name = "XML Interface";
	Initialize( (char*)process_name, 1 );

	// Create the listener socket
	DEBUGOUT( "CreateListener" );
	listen_socket = new XCSocketTransport( NULL );
	listen_socket->Create( AF_INET, SOCK_STREAM, 0 );
	listen_socket->Bind( listen_port );
	listen_socket->Listen( 5 );
};



void XCXMLAppInterface::OnRun() {
	DEBUGOUT( "OnRun" );

	// Send startup messages
	xmlint.Startup();

	// Create the listener thread
	listen_thread = XCCreateThread( ListenerThread, NULL );

	// Enter the message loop
	xmlint.MessageLoop( dest_int );
};


void XCXMLAppInterface::OnCleanup() {
	DEBUGOUT( "OnCleanup" );

	// Causes clients to end
	OnSignalStop();

	// Wait for all sets of threads to complete...

	// Close the listener socket
	if (listen_socket) {
		delete listen_socket;
		listen_socket = NULL;
	};
};


void XCXMLAppInterface::OnSignalStop() {
	DEBUGOUT( "OnSignalStop" );

	// Cancel the message loop
	CancelMessageLoop();

	// Close all open client connections
	socket_info.Lock();
	socket_information* info = socket_thread_list;
	while (info) {
		try {
			if (info->transport)
				info->transport->Close();
		} catch( ... ) {
			// Ignore all errors
		};
		info = info->next;
	};
	socket_info.UnLock();

	// Closing the listener socket will cause the accept call to fail?
	listen_socket->Close();

	// Wait for all client connections to end
	DEBUGOUT( "Wait for client connections" );
	do {
		socket_info.Lock();
		socket_information* info = socket_thread_list;
		THREAD_HANDLE handle;
		if (info)
			handle = info->thread;
		socket_info.UnLock();

		if (info && handle) {
			char temp[ 255 ];
			sprintf( temp, "Wait for handle: %x\n", handle );
			DEBUGOUT( temp );

			// Wait for the thread to exit
			XCJoinThread( handle );
		};
	} while (info);

	// Wait for the listener thread
};


// main function
int main( int argc, char** argv )
{
/**** THIS SECTION SHOULD BE UNCOMMENTED WHEN DOING MEMORY LEAK TESTING
	// Get current flag
	int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );

	// Turn on leak-checking output bit
	tmpFlag |= _CRTDBG_LEAK_CHECK_DF;

	// Turn something else on
	tmpFlag |= _CRTDBG_ALLOC_MEM_DF;

	// Turn SLOW CHECKING ON???
//	tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF;

	// Set flag to the new value
	_CrtSetDbgFlag( tmpFlag );
*****/

	Protea::ShowIntro( "xmlint" );

	if (argc > 1 && !strcmp(argv[1],"-L"))
	{
		Protea::ShowLicense();
		return 0;
	};

	srand( time( NULL ) );
	Protea::Initialize();

	XCServiceDispatcher daemon;

	if (argc == 1) {
		// Run as a service
		daemon.SetService( true );

		// Load our parameter information
		if (!xmlint.LoadParms( argv[0] )) {
			DEBUGOUT( "Unable to load gcs.xml file" );
			cout << "Unable to load gcs.xml file!" << endl;
			exit(1);
		};

		DEBUGOUT( "Set listen port" );
		xmlint.listen_port = xmlint.interfacelist->port;
		xmlint.tracing_mode = 0;

		char asdf[ 255 ];
		sprintf( asdf, "Starting on port %d, host %s, gcs:%d", xmlint.listen_port, xmlint.gcs.machine, xmlint.gcs.port );
		DEBUGOUT( asdf );

		xmlint.hostname = xmlint.gcs.machine;
		xmlint.gcs_port = xmlint.gcs.port;
	} else {
		if (argc == 2) {
			if (strcmp( argv[1], "install" ) ==0) {
				xmlint.Install();
				exit(0);
			} else if (strcmp( argv[1], "uninstall" )==0) {
				xmlint.Uninstall();
				exit(0);
			};
		};

		if ( argc != 5 ) {
			cout << "Usage: xmlint <gcshostname> <gcsport> <listenport> <tracing>" << endl;
			exit( 1 );
		};

		// Run as a program
		daemon.SetService( false );

		// set gcs port
		xmlint.hostname = argv[1];
		xmlint.gcs_port = atol( argv[2] );
		xmlint.listen_port = atol( argv[ 3 ] );
		xmlint.tracing_mode = atol( argv[ 4 ] );
		if (xmlint.tracing_mode)
			printf( "Tracing enabled\n" );
	};

	cout << "Start XML server" << endl;
	DEBUGOUT( "Start XML Server" );

/**** THIS SECTION SHOULD BE UNCOMMENTED WHEN DOING MEMORY LEAK TESTING
	// Turn off ignore Ctl-C by default
		SetConsoleCtrlHandler( NULL, FALSE );
		SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlHandler, TRUE );
******/
	// Run the daemon
	daemon.Add( &xmlint );
	daemon.Register();

	cout << "XML Server ending." << endl;
	DEBUGOUT( "XML Server ending." );

	Protea::Uninitialize();

	return 0;
};
